Comprehensions

Sometimes, it is useful to make some operations on Data Structures and return the same Data Structure. Examples may include squaring every element of a collection, constructing a lookup table and so on. Python provides an easier syntax for doing these tasks. Let’s understand comprehension techiniques by solving some problems

Problem 1

Given a list of integers, Create a new list containing their squares

Classic, C like approach

In [3]:
num = [10,8,3,5,2,7,0,1,4,9,6]
num
Out[3]:
[10, 8, 3, 5, 2, 7, 0, 1, 4, 9, 6]
In [6]:
def square_classic_approach(x):
    """
    Input: x - List of Integers
    Return: A list containing squares of each element of list
    """
    squared = []    # Empty list
    for i in range(len(num)):   # Equivalent to for(i=0;i<num;i++)
        squared.append(num[i]**2) # Power operator
    return squared

Note

Documentation of Python code can be done using docstringss like in above code

In [7]:
square_classic_approach(num)
Out[7]:
[100, 64, 9, 25, 4, 49, 0, 1, 16, 81, 36]

Comprehension Based Approach

In [13]:
def square_pythonic_approach(x):   # Pythonic! :P
    """
    Input: x - List of Integers
    Return: A list containing squares of each element of list
    """
    return [ num**2 for num in x ]
In [9]:
square_pythonic_approach(num)
Out[9]:
[100, 64, 9, 25, 4, 49, 0, 1, 16, 81, 36]

Note

Comprehension increases code readability. Comprehension can be applied to any collection.

Problem 2

Given a list of integers, square them if they are even number and return a list

Classic, C like approach

In [23]:
def square_if_even_classic_approach(x):
    """
    Input: x - List of Integers
    Return: A list containing squares of each element of list if element is even
    """
    squared = []    # Empty list
    for i in range(len(x)):   # Equivalent to for(i=0;i<num;i++)
        if x[i] % 2 == 0:
            squared.append(x[i]**2) # Power operator
        else:
            squared.append(x[i])
    return squared
In [24]:
square_if_even_classic_approach([10,9,2,4,5,67])
Out[24]:
[100, 9, 4, 16, 5, 67]

Observe how list is created and passed on the fly

Comprehension based approach

In [16]:
def square_if_even_pythonic_approach(x):   # Pythonic! :P
    """
    Input: x - List of Integers
    Return: A list containing squares of each element of list if element is even
    """
    return [ num**2 if num % 2 == 0 else num for num in x ]
In [17]:
square_if_even_pythonic_approach(([10,9,2,4,5,67]))
Out[17]:
[100, 9, 4, 16, 5, 67]

Problem 3

Given a string, return the vowels occuring in it, ignoring the case

In [31]:
def vowels_in(string):
    """
    Input: string - a string
    Return: List of vowels occuring in string
    Example:
    >>> vowels_in('Apple`)
    ['a','e']
    """
    # We use a set because it stores unique elements
    l_string = str.lower(string) # Converting to unique form
    vowel_set = {c for c in l_string if c in 'aeiou'}  # Note the imposal of condition
    return sorted(list(vowel_set))
In [29]:
vowels_in('Apple')
Out[29]:
['a', 'e']
In [30]:
vowels_in('Karnataka')
Out[30]:
['a']

Above function can be written in more compact form

In [32]:
def vowels_in_compact(string):
    """
    Input: string - a string
    Return: List of vowels occuring in string
    Example:
    >>> vowels_in('Apple`)
    ['a','e']
    """
    return sorted(list({c for c in str.lower(string) if c in 'aeiou'}))
In [33]:
vowels_in_compact('violin')
Out[33]:
['i', 'o']

Problem 4

Given a string, count the number of occurance of each character, ignoring the case

The nature of the problem makes us to use the dicionary data structure.

In [36]:
def alphabet_occurance_count(string):
    """
    Input: a string
    Output: the number of occurance od f each character in the string
    """
    return {x:string.count(x) for x in string}
In [38]:
alphabet_occurance_count("Hello from Notebook")
Out[38]:
{' ': 2,
 'H': 1,
 'N': 1,
 'b': 1,
 'e': 2,
 'f': 1,
 'k': 1,
 'l': 2,
 'm': 1,
 'o': 5,
 'r': 1,
 't': 1}

Zen revisited

In the beginning of this chapter, we looked at Zen of Python. Now we inspect the things in ``this`` module In the ``this`` module of Python, ``this.s`` contains the encoded text, which has to be decoded using ``this.d``. Decode it

In [39]:
import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Obviously, this is Zen

Now let’s have a look at other things in this module

In [40]:
this.c
Out[40]:
97
In [41]:
this.d
Out[41]:
{'A': 'N',
 'B': 'O',
 'C': 'P',
 'D': 'Q',
 'E': 'R',
 'F': 'S',
 'G': 'T',
 'H': 'U',
 'I': 'V',
 'J': 'W',
 'K': 'X',
 'L': 'Y',
 'M': 'Z',
 'N': 'A',
 'O': 'B',
 'P': 'C',
 'Q': 'D',
 'R': 'E',
 'S': 'F',
 'T': 'G',
 'U': 'H',
 'V': 'I',
 'W': 'J',
 'X': 'K',
 'Y': 'L',
 'Z': 'M',
 'a': 'n',
 'b': 'o',
 'c': 'p',
 'd': 'q',
 'e': 'r',
 'f': 's',
 'g': 't',
 'h': 'u',
 'i': 'v',
 'j': 'w',
 'k': 'x',
 'l': 'y',
 'm': 'z',
 'n': 'a',
 'o': 'b',
 'p': 'c',
 'q': 'd',
 'r': 'e',
 's': 'f',
 't': 'g',
 'u': 'h',
 'v': 'i',
 'w': 'j',
 'x': 'k',
 'y': 'l',
 'z': 'm'}

It looks like a mapping from one character to another… Hmm… Interesting!

In [42]:
this.i
Out[42]:
25
In [43]:
this.s
Out[43]:
"Gur Mra bs Clguba, ol Gvz Crgref\n\nOrnhgvshy vf orggre guna htyl.\nRkcyvpvg vf orggre guna vzcyvpvg.\nFvzcyr vf orggre guna pbzcyrk.\nPbzcyrk vf orggre guna pbzcyvpngrq.\nSyng vf orggre guna arfgrq.\nFcnefr vf orggre guna qrafr.\nErnqnovyvgl pbhagf.\nFcrpvny pnfrf nera'g fcrpvny rabhtu gb oernx gur ehyrf.\nNygubhtu cenpgvpnyvgl orngf chevgl.\nReebef fubhyq arire cnff fvyragyl.\nHayrff rkcyvpvgyl fvyraprq.\nVa gur snpr bs nzovthvgl, ershfr gur grzcgngvba gb thrff.\nGurer fubhyq or bar-- naq cersrenoyl bayl bar --boivbhf jnl gb qb vg.\nNygubhtu gung jnl znl abg or boivbhf ng svefg hayrff lbh'er Qhgpu.\nAbj vf orggre guna arire.\nNygubhtu arire vf bsgra orggre guna *evtug* abj.\nVs gur vzcyrzragngvba vf uneq gb rkcynva, vg'f n onq vqrn.\nVs gur vzcyrzragngvba vf rnfl gb rkcynva, vg znl or n tbbq vqrn.\nAnzrfcnprf ner bar ubaxvat terng vqrn -- yrg'f qb zber bs gubfr!"

Wow!…Looks like encoded text.

We will decode it using this.d mapping

In [47]:
decoded = ''.join([this.d[c] if str.isalnum(c) else c for c in this.s]) # join() joins the iterable with string
print(decoded)
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

This is again *Zen of Python!*

In fact ``this`` module uses a function to build the Translation Table ``d`` in it’s __init.py__ to print the *Zen*. The encoding done here is rot13 encoding. We will look about modules in upcoming chapters.

Fibonacci Again

Now you have understood the lists and operations. Let’s look at a recursive Fibonacci Number Generator

In [53]:
def fibonacci_recursive(n,first=0,second=1):
    return [] if n == 0 else [first] + fibonacci_recursive(n - 1, second, first + second)
In [54]:
fibonacci_recursive(5)
Out[54]:
[0, 1, 1, 2, 3]

Filtering Lists - Need for lambdas

In Python, functions are also objects. It means that you can pass them to other function like a variable. This flexibility of functions allows us to do many useful tasks. filtering a collection is one of them.

Problem : Find even numbers in a given sequence

Solution 1: Use List comprehension

In [55]:
x = [0,1,3,5,8,7,6]
In [57]:
evens = [i for i in x if i%2 == 0]
evens
Out[57]:
[0, 8, 6]

Solution 2: Use filter with functions

filter function takes a list and a function returning bool as argument and filter’s the list, returns the iterator through filtered list. One can use list() to convert iterator to a list

Usage

result = list(filter(condition,collection))

condition is a boolean function that takes an element as input

In [58]:
 def is_even(item):
    return item % 2 == 0
In [61]:
list(filter(is_even,x))
Out[61]:
[0, 8, 6]

Solution 3: Use \lambdas

In previous example, we passed a function object to filter(). The same case happens in many situations. In some cases function to be passed might be too short like is_even(). In this case lambdas can be used. lambdas create function in place.

Usage:

function_name = lambda argument_list : executable_statements

This has the same effect as that of

def function_name(argument_list):
    executable_statements

Now our is_even function can be defined in terms of lambdas

is_even = lambda x : x % 2 == 0 # Note that return may be omitted

If multi-line statements are needed, Statements can be put inside ()s or line can be extended with \s

In [62]:
list(filter(lambda x: x % 2 == 0,x))
Out[62]:
[0, 8, 6]

Lambdas are a fundamental concept of Functional Programming where every task is achieved via a function. They constitute the basis of a branch of Mathematics and Computation Theory called :math:`lambda` calculus.

As a final thought, we shall see Recursive Fibonacci Generator in terms of lambdas

In [5]:
fibonacci_lambda = \
    lambda n,first=0,second=1 :\
            [] if n == 0 \
               else \
                  [first] + fibonacci_lambda(n - 1, second, first + second)
In [6]:
fibonacci_lambda(4)
Out[6]:
[0, 1, 1, 2]

Note that lines are broken with \